using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Text;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Http;
using Microsoft.AspNetCore.Mvc.ModelBinding;
using Microsoft.AspNetCore.WebUtilities;
using Microsoft.Net.Http.Headers;

namespace housingsafety.server.Helpers
{
    public static class FileHelpers
    {
        // For more file signatures, see the File Signatures Database (https://www.filesignatures.net/)
        // and the official specifications for the file types you wish to add.
        private static readonly Dictionary<string, List<byte[]>> FileSignature = new Dictionary<string, List<byte[]>>
        {
            { ".amr", new List<byte[]> { new byte[] { 0x23, 0x21, 0x41, 0x4D, 0x52 } } },
            { ".wav", new List<byte[]> { new byte[] { 0x52, 0x49, 0x46, 0x46 } } },
            { ".midi", new List<byte[]> { new byte[] { 0x4D, 0x54, 0x68, 0x64 } } },
            { ".m4a", new List<byte[]> { new byte[] { 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41 } } },
            { ".flac", new List<byte[]> { new byte[] { 0x66, 0x4C, 0x61, 0x43, 0x00, 0x00, 0x00, 0x22 } } },
            { ".gif", new List<byte[]> { new byte[] { 0x47, 0x49, 0x46, 0x38 } } },
            { ".mp3", new List<byte[]>
                {
                    new byte[] { 0x49, 0x44, 0x33 },//mp3
                    new byte[] { 0x4F, 0x67, 0x67, 0x53, 0x00, 0x02, 0x00 , 0x00 },//ogg
                }
            },
            { ".mp4", new List<byte[]>
                {
                    new byte[] { 0x00, 0x00, 0x00, 0x1C, 0x66, 0x74, 0x79, 0x70, 0x6D, 0x70, 0x34 },//mp4
                    new byte[] { 0x00, 0x00, 0x00, 0x20, 0x66, 0x74, 0x79, 0x70, 0x4D, 0x34, 0x41 },//m4a
                }
            },
            { ".png", new List<byte[]>
                {
                    new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A },//png
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
                }
            },
            { ".jpeg", new List<byte[]>
                {
                    new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A },
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xE2 },
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xE3 },
                }
            },
            { ".jpg", new List<byte[]>
                {
                    new byte[] { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A },
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xE0 },
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xE1 },
                    new byte[] { 0xFF, 0xD8, 0xFF, 0xE8 },
                }
            },
            { ".zip", new List<byte[]>
                {
                    new byte[] { 0x50, 0x4B, 0x03, 0x04 },
                    new byte[] { 0x50, 0x4B, 0x4C, 0x49, 0x54, 0x45 },
                    new byte[] { 0x50, 0x4B, 0x53, 0x70, 0x58 },
                    new byte[] { 0x50, 0x4B, 0x05, 0x06 },
                    new byte[] { 0x50, 0x4B, 0x07, 0x08 },
                    new byte[] { 0x57, 0x69, 0x6E, 0x5A, 0x69, 0x70 },
                }
            },
        };

        // **WARNING!**
        // In the following file processing methods, the file's content isn't scanned.
        // In most production scenarios, an anti-virus/anti-malware scanner API is
        // used on the file before making the file available to users or other
        // systems. For more information, see the topic that accompanies this sample
        // app.

        public static async Task<byte[]> ProcessFormFile(IFormFile formFile,
            StringBuilder stringBuilder, string[] permittedExtensions,
            long sizeLimit)
        {
            var fieldDisplayName = string.Empty;

            // Don't trust the file name sent by the client. To display
            // the file name, HTML-encode the value.
            var trustedFileNameForDisplay = WebUtility.HtmlEncode(
                formFile.FileName);

            // Check the file length. This check doesn't catch files that only have 
            // a BOM as their content.
            if (formFile.Length == 0)
            {
                stringBuilder.Append($"{formFile.Name}{fieldDisplayName}({trustedFileNameForDisplay}) is empty.");

                return new byte[0];
            }

            if (formFile.Length > sizeLimit)
            {
                var megabyteSizeLimit = sizeLimit / 1048576;
                stringBuilder.Append($"{formFile.Name}{fieldDisplayName}({trustedFileNameForDisplay}) exceeds " +
                    $"{megabyteSizeLimit:N1} MB.");

                return new byte[0];
            }

            try
            {
                using (var memoryStream = new MemoryStream())
                {
                    await formFile.CopyToAsync(memoryStream);

                    // Check the content length in case the file's only
                    // content was a BOM and the content is actually
                    // empty after removing the BOM.
                    if (memoryStream.Length == 0)
                    {
                        stringBuilder.Append($"{formFile.Name}{fieldDisplayName}({trustedFileNameForDisplay}) is empty.");
                    }

                    if (!IsValidFileExtensionAndSignature(
                        formFile.FileName, stringBuilder, memoryStream, permittedExtensions))
                    {
                        stringBuilder.Append($"{formFile.Name}{fieldDisplayName}({trustedFileNameForDisplay}) file " +
                            "type isn't permitted or the file's signature " +
                            "doesn't match the file's extension.");
                    }
                    else
                    {
                        return memoryStream.ToArray();
                    }
                }
            }
            catch (Exception ex)
            {
                stringBuilder.Append($"{formFile.Name}{fieldDisplayName}({trustedFileNameForDisplay}) upload failed. " +
                    $"Please contact the Help Desk for support. Error: {ex.HResult}");
                // Log the exception
            }

            return new byte[0];
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="section"></param>
        /// <param name="contentDisposition"></param>
        /// <param name="stringBuilder"></param>
        /// <param name="permittedExtensions"></param>
        /// <returns></returns>
        public static bool ProcessStreamedFile(
            MultipartSection section, ContentDispositionHeaderValue contentDisposition,
            StringBuilder stringBuilder, string[] permittedExtensions)
        {
            try
            {
                {
                    var fileMultipartSection = section.AsFileSection();

                    if (!IsValidFileExtensionAndSignature(contentDisposition.FileName.Value, stringBuilder, fileMultipartSection.FileStream, permittedExtensions))
                    {
                        stringBuilder.Append("The file's signature Is Invalid.");
                    }
                    else
                    {
                        section.Body.Position = 0;
                        return true;
                    }
                }
            }
            catch (Exception ex)
            {
                stringBuilder.Append("The upload failed. Please contact the Help Desk " +
                    $" for support. Error: {ex.HResult}");
                // Log the exception
            }

            return false;
        }

        private static bool IsValidFileExtensionAndSignature(string fileName, StringBuilder stringBuilder, Stream data
            , string[] permittedExtensions)
        {
            if (string.IsNullOrEmpty(fileName) || data == null)
            {
                stringBuilder.Append("The file Name or data is null.");
                return false;
            }

            var ext = Path.GetExtension(fileName).ToLowerInvariant();
            if (string.IsNullOrEmpty(ext) || !permittedExtensions.Contains(ext))
            {
                stringBuilder.Append("The file type isn't permitted.");
                return false;
            }

            if (ext.Equals(".txt") || ext.Equals(".csv") || ext.Equals(".prn"))
            {
                return true;
            }

            // Uncomment the following code block if you must permit
            // files whose signature isn't provided in the _fileSignature
            // dictionary. We recommend that you add file signatures
            // for files (when possible) for all file types you intend
            // to allow on the system and perform the file signature
            // check.

            if (!FileSignature.ContainsKey(ext))
            {
                stringBuilder.Append("The file's signature doesn't match the file's extension.");
                return false;
            }

            // File signature check
            // --------------------
            // With the file signatures provided in the FileSignature
            // dictionary, the following code tests the input content's
            // file signature.
            var signatures = FileSignature[ext];
            var max = signatures.Max(m => m.Length);
            data.Position = 0;

            using (var reader = new BinaryReader(data))
            {
                var headerBytes = reader.ReadBytes(max);
                var chk = signatures.Any(signature =>
                     headerBytes.Take(signature.Length).SequenceEqual(signature));

                if (!chk)
                {
                    stringBuilder.Append($"The file's signature is: {BitConverter.ToString(headerBytes)}.");
                }

                return chk;
            }
        }
    }
}
